package zheezes.socket.impl.gd;

import org.apache.log4j.Logger;

import zheezes.protocol.Frame;
import zheezes.protocol.impl.gd.Ctrl21Frame;
import zheezes.protocol.impl.gd.Ctrl22Frame;
import zheezes.protocol.impl.gd.Ctrl24Frame;
import zheezes.protocol.impl.gd.CtrlA1Frame;
import zheezes.protocol.impl.gd.CtrlA2Frame;
import zheezes.protocol.impl.gd.CtrlA4Frame;
import zheezes.protocol.impl.gd.GdFrame;
import zheezes.protocol.impl.gd.GdMasterAddr;
import zheezes.socket.Postman;
import zheezes.socket.Transput;


public class GdPostman extends Postman {
	private static Logger logger = Logger.getLogger(GdPostman.class);

	private FrameSequence[] seqs;
	private volatile int cur = 0;
	private GdMasterAddr msta;
	private byte[] pwd = new byte[3];

	public GdPostman(GdMasterAddr msta) {
		this.msta = msta;
		initSequence();
	}
	
	private int initSequence() {
		int no = 0x01;
		seqs = new FrameSequence[0x7F];
		for (int i = 0; i < seqs.length; i++) {
			seqs[i] = new FrameSequence(no++);
		}
		return seqs.length;
	}
	
	// TODO to support one-request and multi-response
	@Override
	public void handle(Frame frame) {
		GdFrame frm = (GdFrame) frame;
		FrameSequence seq;
		Transput transput;

		logger.debug(String.format("Postman[%s] recv fseq = %02X", desc(), frm.getFseq()));
		if ((seq = findNo(frm.getFseq())) != null) {
			if((transput = seq.getTransput()) != null) {
				// transput.setRecv(new Frame[] { frame });
				transput.addRecv(frame);
				synchronized (transput) {
					transput.notifyAll();
				}
				logger.debug(String.format("wakeup %s", transput.toString()));
			}
		} else {
			logger.debug("there is no notifier to wakeup");
		}
	}

	@Override
	public int login() {
		logger.info(String.format("%s login", desc()));
		CtrlA1Frame frame = new CtrlA1Frame(pwd);
		frame.setMsta(msta);
		Frame[] frames = null;
		if (sendFrame(frame) == 0) {
			if ((frames = recvFrame(frameSo)) != null) {
				for (Frame f : frames) {
					if (frame.match(f) && f instanceof Ctrl21Frame) {
						logger.info(String.format("%s login success", desc()));
						return 0;
					}
				}
			}
		}
		return -1;
	}
	
	@Override
	public int logout() {
		logger.info(String.format("%s logout", desc()));
		CtrlA2Frame frame = new CtrlA2Frame();
		frame.setMsta(msta);
		Frame[] frames = null;
		if (sendFrame(frame) == 0) {
			if ((frames = recvFrame(frameSo)) != null) {
				for (Frame f : frames) {
					if (frame.match(f) && f instanceof Ctrl22Frame) {
						logger.info(String.format("%s logout success", desc()));
						return 0;
					}
				}
			}
		}
		
		return -1;
	}
	
	protected int heartbeat() {
		logger.info(String.format("%s heartbeat", desc()));
		CtrlA4Frame frame = new CtrlA4Frame();
		frame.setMsta(msta);
		Frame[] frames = null;
		if (sendFrame(frame) == 0) {
			if ((frames = recvFrame(frameSo)) != null) {
				for (Frame f : frames) {
					if (frame.match(f) && f instanceof Ctrl24Frame) {
						logger.info(String.format("%s heartbeat success", desc()));
						return 0;
					}
				}
			}
		}

		return -1;
	}

	@Override
	public Transput sendFrame(Frame frame, int option) {
		FrameSequence seq = null;
		if ((seq = acquireNo()) != null) {
			logger.debug(String.format("Postman[%s] send fseq = %02X", desc(), seq.getNo()));
			if (frame instanceof GdFrame) {
				GdFrame frm = (GdFrame) frame;
				frm.setMsta(msta);
				frm.setFseq((byte) seq.getNo());
				// seq.setMailbox(mailbox);
				seq.setTimeout(frameSo);
				if(sendFrame(frame) == 0) { // if success communication, new transput info
					Transput transput = new Transput();
					transput.setPostman(this);
					transput.setSend(frame);
					transput.setTimeout(frameSo);
					seq.setTransput(transput);
					return transput;
				}
			}
		}
		return null;
	}

	/* private */
	/**
	 * always use the next fseq to optimize performance
	 */
	private synchronized FrameSequence acquireNo() {
		for(int i = 0, len = seqs.length; i < len; i++ ) {
			FrameSequence seq = seqs[cur++ % len];
			if (seq.acquire()) {
				return seq;
			}
		}
		return null;
	}

	private FrameSequence findNo(int no) {
		for (FrameSequence seq : seqs) {
			if (seq.getNo() == no) {
				return seq;
			}
		}
		return null;
	}

	/* standard set/get */
	public void setPwd(byte[] pwd) {
		this.pwd = pwd.clone();
	}
	
	@Override
	public String desc() {
		return String.format("%02X", msta.getBytes()[0]);
	}
}
